﻿using System.Linq;
using MetroIoc;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using metroioc.tests.Artefacts;

namespace metroioc.tests
{
    [TestClass]
    public class ResolutionTests
    {
        [TestMethod]
        public void ResolvingUnregisteredInterfaceThrowsException()
        {
            Assert.ThrowsException<ResolutionException>(
                () => new MetroContainer().Resolve<IFoo>());
        }

        [TestMethod]
        public void ContainerCannotResolveTypeIfParameterIsUnregistered()
        {
            Assert.ThrowsException<ResolutionException>(
                () => new MetroContainer().Resolve<Bar>());
        }

        [TestMethod]
        public void ResoveCreatesItemOfRequiredType()
        {
            var iocContainer = new MetroContainer()
                .Register<IFoo, Foo>();

            var instance = iocContainer.Resolve<IFoo>();

            Assert.IsInstanceOfType(instance, typeof(Foo));
        }

        [TestMethod]
        public void ResolveByKeyGetsCorrectInstance()
        {
            var foo1 = new Foo();
            var foo2 = new Foo();

            var iocContainer = new MetroContainer()
                .RegisterInstance<IFoo>(foo1)
                .RegisterInstance<IFoo>(foo2, "theKey");

            var instance = iocContainer.Resolve<IFoo>("theKey");

            Assert.AreNotSame(instance, foo1);
            Assert.AreSame(instance, foo2);
        }

        [TestMethod]
        public void ResolveByKeyGetsCorrectInstances()
        {
            var foo1 = new Foo();
            var foo2 = new Foo();

            var iocContainer = new MetroContainer()
                .RegisterInstance<IFoo>(foo1, "theKey1")
                .RegisterInstance<IFoo>(foo2, "theKey2");

            var instance1 = iocContainer.Resolve<IFoo>("theKey1");
            var instance2 = iocContainer.Resolve<IFoo>("theKey2");

            Assert.AreSame(instance1, foo1);
            Assert.AreSame(instance2, foo2);
        }


        [TestMethod]
        public void ContainerCanResolveUnregisteredConcreteTypes()
        {
            var iocContainer = new MetroContainer();

            var instance = iocContainer.Resolve<Foo>();
            Assert.IsNotNull(instance);
            Assert.IsInstanceOfType(instance, typeof(Foo));
        }

        [TestMethod]
        public void ContainerCanResolveAConcreteTypeWithoutDefaultConstructor()
        {
            var iocContainer = new MetroContainer()
                .Register<IFoo, Foo>();

            var instance = iocContainer.Resolve<IFoo>();

            Assert.IsNotNull(instance);
            Assert.IsInstanceOfType(instance, typeof(Foo));
        }

        [TestMethod]
        public void ContainerWillChainDependencyInjection()
        {
            var iocContainer = new MetroContainer()
                .Register<IFoo, Foo>();

            var instance = iocContainer.Resolve<Bar>();
            Assert.IsNotNull(instance);
            Assert.IsNotNull(instance.Foo);
        }

        [TestMethod]
        public void ContainerWillResolveSpecificInstancesInChainedResolution()
        {
            var foo1 = new Foo();
            var iocContainer = new MetroContainer()
                .RegisterInstance<IFoo>(foo1);

            var instance = iocContainer.Resolve<Bar>();
            Assert.IsNotNull(instance);
            Assert.AreSame(foo1, instance.Foo);
        }

        [TestMethod]
        public void ContainerInstantiatesConstructorWithMostParameters()
        {
            var iocContainer = new MetroContainer()
                .Register<IFirst, First>()
                .Register<ISecond, Second>()
                .Register<IThird, Third>();

            var instance = iocContainer.Resolve<OverloadedTestClass>();
            Assert.IsNotNull(instance.First);
            Assert.IsNotNull(instance.Second);
            Assert.IsNotNull(instance.Third);
        }


        [TestMethod]
        public void ContainerFindsPublicConstructorWithMostParameters()
        {
            var iocContainer = new MetroContainer()
                .Register<IFirst, First>()
                .Register<ISecond, Second>()
                .Register<IThird, Third>();

            var instance = iocContainer.Resolve<OverloadedTestClassWithInternalConstructor>();
            Assert.IsNotNull(instance.First);
            Assert.IsNotNull(instance.Second);
            Assert.IsNull(instance.Third);
        }


        [TestMethod]
        public void ContainerInstantiatesConstructorMarkedWithAttribute()
        {
            var iocContainer = new MetroContainer()
                .Register<IFirst, First>();

            var instance = iocContainer.Resolve<AttributedTestClass>();

            Assert.IsTrue(instance.CorrectConstructorWasUsed, "Incorrect constructor was used");
        }


        [TestMethod]
        public void InjectionWorksOnProperties()
        {
            const string expectedName = "Fabrikam inc.";

            var iocContainer = new MetroContainer()
                .RegisterInstance(expectedName, "metroioc.tests.Artefacts.TestClassWithProperty.CustomerName");

            var instance = iocContainer.Resolve<TestClassWithProperty>();
            Assert.AreEqual(expectedName, instance.CustomerName);
        }

        [TestMethod]
        public void ContainerHandlesMultipleProperties()
        {
            const string expectedName = "Fabrikam inc.";

            var iocContainer = new MetroContainer()
                .RegisterInstance(expectedName, "metroioc.tests.Artefacts.TestClassWithProperties.CustomerName")
                .Register<IFirst, First>()
                .Register<ISecond, Second>();

            var instance = iocContainer.Resolve<TestClassWithProperties>();

            Assert.AreEqual(expectedName, instance.CustomerName);
            Assert.IsInstanceOfType(instance.FirstProperty, typeof(First));
            Assert.IsInstanceOfType(instance.SecondProperty, typeof(Second));
        }

        [TestMethod]
        public void ContainerHandlesMultipleInjectionMethods()
        {
            const string expectedName = "Fabrikam inc.";

            var iocContainer = new MetroContainer()
                .RegisterInstance(expectedName)
                .Register<IFirst, First>()
                .Register<ISecond, Second>();

            var instance = iocContainer.Resolve<TestClassWithPropertiesAndConstructor>();

            Assert.AreEqual(expectedName, instance.CustomerName);
            Assert.IsInstanceOfType(instance.First, typeof(First));
            Assert.IsInstanceOfType(instance.Second, typeof(Second));
        }

        [TestMethod]
        public void PropertyInjectionThrowsExceptionForUnregisteredDependency()
        {
            const string expectedName = "Fabrikam inc.";

            var iocContainer = new MetroContainer()
                .RegisterInstance(expectedName)
                .Register<IFirst, First>();

            // Do not register this one... 
            // iocContainer.Register<ISecond, Second>();

            Assert.ThrowsException<ResolutionException>(
                () => iocContainer.Resolve<TestClassWithProperties>());
        }

        [TestMethod]
        public void ContainerRetrievesInstancesByKey()
        {
            var iocContainer = new MetroContainer()
                .Register<IFoo, Foo>("Foo", new Singleton());

            var instance = iocContainer.Resolve(null, "Foo");
            Assert.IsInstanceOfType(instance, typeof(Foo));
            Assert.IsNotNull(instance);
        }

        [TestMethod]
        public void ContainerRetrievesInstancesByKeyWithSingleParameterTypeRegistration()
        {
            var iocContainer = new MetroContainer()
                .Register<Foo2>("Foo2", new Singleton());

            var instance = iocContainer.Resolve(null, "Foo2");
            Assert.IsInstanceOfType(instance, typeof(Foo2));
            Assert.IsNotNull(instance);
        }

        [TestMethod]
        public void GenericTryResolveUnregisteredInterfaceReturnsNull()
        {
            IContainer iocContainer = new MetroContainer();

            var instance = iocContainer.TryResolve<IFoo>();

            Assert.IsNull(instance, "Instance should be null");
        }

        [TestMethod]
        public void TryResolveUnregisteredInterfaceReturnsNull()
        {
            IContainer iocContainer = new MetroContainer();

            var instance = iocContainer.TryResolve(typeof(IFoo));

            Assert.IsNull(instance, "Instance should be null");
        }

        [TestMethod]
        public void GenericTryResolveUnregisteredInterfaceWithKeyReturnsNull()
        {
            IContainer iocContainer = new MetroContainer();

            var instance = iocContainer.TryResolve<IFoo>("SomeKey");

            Assert.IsNull(instance, "Instance should be null");
        }

        [TestMethod]
        public void TryResolveUnregisteredInterfaceWithKeyReturnsNull()
        {
            IContainer iocContainer = new MetroContainer();

            var instance = iocContainer.TryResolve(typeof(IFoo), "SomeKey");

            Assert.IsNull(instance, "Instance should be null");
        }

        [TestMethod]
        public void GenericTryResolveRegisteredInterfaceWithInvalidKeyReturnsNull()
        {
            var iocContainer = new MetroContainer()
                .Register<IFoo, Foo>();

            var instance = iocContainer.TryResolve<IFoo>("SomeKey");

            Assert.IsNull(instance, "Instance should be null");
        }

        [TestMethod]
        public void TryResolveRegisteredInterfaceWithInvalidKeyReturnsNull()
        {
            var iocContainer = new MetroContainer()
                .Register<IFoo, Foo>();

            var instance = iocContainer.TryResolve(typeof(IFoo), "SomeKey");

            Assert.IsNull(instance, "Instance should be null");
        }

        [TestMethod]
        public void GenericTryResolveRegisteredInterfaceReturnsInstance()
        {
            var iocContainer = new MetroContainer()
                .Register<IFoo, Foo>();

            var instance = iocContainer.TryResolve<IFoo>();

            Assert.IsNotNull(instance, "Instance should not be null");
            Assert.IsInstanceOfType(instance, typeof(IFoo));
        }

        [TestMethod]
        public void TryResolveRegisteredInterfaceReturnsInstance()
        {
            var iocContainer = new MetroContainer()
                .Register<IFoo, Foo>();

            var instance = iocContainer.TryResolve(typeof(IFoo));

            Assert.IsNotNull(instance, "Instance should not be null");
            Assert.IsInstanceOfType(instance, typeof(IFoo));
        }

        [TestMethod]
        public void GenericResolveAllUnregisteredTypeReturnsEmptyCollection()
        {
            var ioc = new MetroContainer();

            var instances = ioc.ResolveAll<Foo>();

            Assert.IsNotNull(instances);
            Assert.AreEqual(0, instances.Count());
        }

        [TestMethod]
        public void ResolveAllUnregisteredTypeReturnsEmptyCollection()
        {
            var ioc = new MetroContainer();

            var instances = ioc.ResolveAll(typeof(Foo));

            Assert.IsNotNull(instances);
            Assert.AreEqual(0, instances.Count());
        }

        [TestMethod]
        public void GenericResolveAllReturnsSingleRegisteredTypeInACollection()
        {
            var ioc = new MetroContainer()
                .Register<IFoo, Foo>();

            var instances = ioc.ResolveAll<IFoo>();

            Assert.IsNotNull(instances);
            Assert.AreEqual(1, instances.Count());
            Assert.IsInstanceOfType(instances.Single(), typeof(IFoo), "Registered instance should be an IFoo");
        }

        [TestMethod]
        public void ResolveAllReturnsSingleRegisteredTypeInACollection()
        {
            var ioc = new MetroContainer()
                .Register<IFoo, Foo>();

            var instances = ioc.ResolveAll(typeof(IFoo));

            Assert.IsNotNull(instances);
            Assert.AreEqual(1, instances.Count());
            Assert.IsInstanceOfType(instances.Single(), typeof(IFoo), "Registered instance should be an IFoo");
        }

        [TestMethod]
        public void GenericResolveAllReturnsAllRegisteredTypesInACollection()
        {
            var ioc = new MetroContainer()
                .Register<IFoo, Foo>("One")
                .RegisterInstance<IFoo>(new Foo(), "Two")
                .Register<IFoo, Foo>("Three", new Singleton());

            var instances = ioc.ResolveAll<IFoo>();

            Assert.IsNotNull(instances);
            Assert.AreEqual(3, instances.Count());
        }

        [TestMethod]
        public void ResolveAllReturnsAllRegisteredTypesInACollection()
        {
            var ioc = new MetroContainer()
                .Register<IFoo, Foo>("One")
                .RegisterInstance<IFoo>(new Foo(), "Two")
                .Register<IFoo, Foo>("Three", new Singleton());

            var instances = ioc.ResolveAll(typeof(IFoo));

            Assert.IsNotNull(instances);
            Assert.AreEqual(3, instances.Count());
        }
    }
}
